paint-brush
जानें कि रिलेशनल डेटाबेस माइग्रेशन का उपयोग क्यों और कैसे करेंद्वारा@artemsutulov
1,644 रीडिंग
1,644 रीडिंग

जानें कि रिलेशनल डेटाबेस माइग्रेशन का उपयोग क्यों और कैसे करें

द्वारा Artem Sutulov7m2022/07/16
Read on Terminal Reader
Read this story w/o Javascript

बहुत लंबा; पढ़ने के लिए

परिचय बैकएंड सेवाओं को विकसित करते समय, यदि डेटाबेस एकीकरण गलत तरीके से लागू किया जाता है, तो समस्याएँ पैदा करना आसान है। आजकल, डेवलपर्स ज्यादातर दो दृष्टिकोणों का उपयोग करते हैं: स्वचालित पीढ़ी, उदाहरण के लिए, जेपीए या हाइबरनेट - डेटाबेस शुरू होता है और कक्षाओं और वर्तमान डीबी स्थिति की तुलना करके अद्यतित रहता है; यदि परिवर्तन की आवश्यकता है, तो वे लागू होते हैं। इसका मतलब है कि हाइबरनेट इकाई में, हम नया कॉलम जोड़ते हैं: @Column(name = "receive_notifications", nullable = false) निजी बूलियन रिसीव नोटिफिकेशन; ऐप शुरू करने के बाद, हम लॉग में त्रुटि देखते हैं और कोई नया कॉलम नहीं देखते हैं। प्रत्येक डेवलपर को एक अलग वातावरण की आवश्यकता होती है। लेकिन अगली बार माइग्रेशन पर विचार करना बेहतर होगा क्योंकि यह जावा संस्थाओं को राहत देगा, अतिरिक्त जिम्मेदारी को हटा देगा, और डीडीएल पर बहुत अधिक नियंत्रण के साथ आपको लाभान्वित करेगा। आप GitHub पर पूरी तरह से काम करने वाला उदाहरण पा सकते हैं।
featured image - जानें कि रिलेशनल डेटाबेस माइग्रेशन का उपयोग क्यों और कैसे करें
Artem Sutulov HackerNoon profile picture

बैकएंड सेवाओं को विकसित करते समय, यदि डेटाबेस एकीकरण गलत तरीके से लागू किया जाता है, तो समस्याएँ पैदा करना आसान है। यह लेख आपको आधुनिक सेवाओं में रिलेशनल डेटाबेस के साथ काम करने के लिए कुछ सर्वोत्तम अभ्यास बताएगा और आपको यह भी दिखाएगा कि स्वचालित रूप से अप-टू-डेट स्कीमा बनाना और रखना एक अच्छा विचार नहीं है।


मैं डेटाबेस माइग्रेशन के लिए फ्लाईवे , आसान सेटअप के लिए स्प्रिंग बूट और उदाहरण डेटाबेस के रूप में H2 का उपयोग करूंगा।


मैंने इस बारे में बुनियादी जानकारी नहीं दी कि माइग्रेशन क्या हैं और वे कैसे काम करते हैं। यहाँ फ्लाईवे के अच्छे लेख हैं:


  • माइग्रेशन क्या हैं , इसके बारे में बुनियादी जानकारी
  • फ्लाईवे हुड के नीचे कैसे काम करता है।

समस्या

बहुत समय पहले, डेवलपर्स एप्लिकेशन से अलग स्क्रिप्ट लागू करके डेटाबेस को इनिशियलाइज़ और अपडेट कर रहे थे। हालांकि, इन दिनों कोई भी ऐसा नहीं करता है क्योंकि इसे विकसित करना और एक उचित स्थिति में बनाए रखना कठिन है, जिससे गंभीर परेशानी होती है।


आजकल, डेवलपर्स ज्यादातर दो दृष्टिकोणों का उपयोग करते हैं:


  1. स्वचालित पीढ़ी, उदाहरण के लिए, जेपीए या हाइबरनेट - डेटाबेस शुरू होता है और कक्षाओं और वर्तमान डीबी स्थिति की तुलना करके अद्यतित रहता है; यदि परिवर्तन की आवश्यकता है, तो वे लागू होते हैं।

  2. डेटाबेस माइग्रेशन - डेवलपर्स डेटाबेस को क्रमिक रूप से अपडेट करते हैं, और स्टार्टअप, डेटाबेस माइग्रेशन पर परिवर्तन स्वचालित रूप से लागू होते हैं।

    इसके अलावा, अगर हम स्प्रिंग के बारे में बात करते हैं, तो बॉक्स से बाहर एक बुनियादी डेटाबेस आरंभीकरण है, लेकिन यह फ्लाईवे या लिक्विबेस जैसे इसके एनालॉग्स की तुलना में कम उन्नत है।


हाइबरनेट स्वचालित जनरेटिंग

यह प्रदर्शित करने के लिए कि यह कैसे काम करता है आइए एक साधारण उदाहरण का उपयोग करें। तीन फ़ील्ड वाले टेबल उपयोगकर्ता - id , user_name , email :


उपयोगकर्ता तालिका प्रतिनिधित्व


आइए हाइबरनेट द्वारा स्वचालित रूप से उत्पन्न एक पर एक नज़र डालें।

हाइबरनेट इकाई:

 @Entity @Table(name = "users") public class User { @Id @GeneratedValue private UUID id; @Column(name = "user_name", length = 64, nullable = false) private String userName; @Column(name = "email", length = 128, nullable = true) private String email; }

स्कीमा को अद्यतित रखने के लिए हमें स्प्रिंग बूट कॉन्फ़िगरेशन में इस पंक्ति की आवश्यकता है और यह स्टार्टअप पर करना शुरू कर देता है:

 jpa.hibernate.ddl-auto=update

और आवेदन शुरू होने पर हाइबरनेट से लॉग इन करें:

Hibernate: create table users (id binary(255) not null, email varchar(128), user_name varchar(64) not null, primary key (id))


स्वचालित जनरेटिंग के बाद, इसने 255 के अधिकतम आकार के साथ binary के रूप में id बनाई, यह बहुत अधिक है क्योंकि UUID में केवल 36 वर्ण होते हैं। तो हमें इसके बजाय UUID प्रकार का उपयोग करने की आवश्यकता है, हालांकि, यह इस तरह से उत्पन्न नहीं होता है। इस एनोटेशन को जोड़कर इसे ठीक किया जा सकता है:

 @Column(name = "id", columnDefinition = "uuid")


हालाँकि, हम पहले से ही कॉलम में SQL परिभाषा लिख रहे हैं, जो SQL से Java तक एब्स्ट्रैक्शन को तोड़ता है।


और आइए कुछ उपयोगकर्ताओं के साथ तालिका भरें:

 insert into users (id, user_name, email) values ('297a848d-d406-4055-8a6f-4a4118a44001', 'Artem', null); insert into users (id, user_name, email) values ('921a9d42-bf14-4c3f-9893-60f79cdd0825', 'Antonio', '[email protected]');

एक नया कॉलम जोड़ना

उदाहरण के लिए, आइए कल्पना करें कि कुछ समय बाद हम अपने ऐप में नोटिफिकेशन जोड़ना चाहते हैं, और इसके परिणामस्वरूप ट्रैक करना चाहते हैं कि कोई उपयोगकर्ता उन्हें प्राप्त करना चाहता है या नहीं। इसलिए हमने तालिका उपयोगकर्ताओं के लिए एक कॉलम receive_notifications जोड़ने और इसे गैर-शून्य बनाने का निर्णय लिया।


एक नया कॉलम जोड़ने के बाद उपयोगकर्ता तालिका


इसका मतलब है कि हाइबरनेट इकाई में, हम नया कॉलम जोड़ते हैं:

 @Column(name = "receive_notifications", nullable = false) private Boolean receiveNotifications;


ऐप शुरू करने के बाद, हम लॉग में त्रुटि देखते हैं और कोई नया कॉलम नहीं देखते हैं। ऐसा इसलिए है क्योंकि तालिका खाली नहीं है, और हमें मौजूदा पंक्तियों के लिए एक डिफ़ॉल्ट मान सेट करने की आवश्यकता है:

Error executing DDL "alter table users add column receive_notifications boolean not null" via JDBC Statement


हम फिर से SQL कॉलम परिभाषा जोड़कर एक डिफ़ॉल्ट मान सेट कर सकते हैं:

 columnDefinition = "boolean default true"


और हाइबरनेट लॉग से, हम देख सकते हैं कि इसने काम किया:

Hibernate: alter table users add column receive_notifications boolean default true not null


हालांकि, आइए कल्पना करें कि हमें कुछ अधिक जटिल होने के लिए receive_notifications की आवश्यकता है, उदाहरण के लिए, सही या गलत, इस पर निर्भर करता है कि ईमेल भरा है या नहीं। उस तर्क को केवल हाइबरनेट के साथ लागू करना असंभव है, इसलिए हमें वैसे भी माइग्रेशन की आवश्यकता है।


get_notifications के लिए अधिक जटिल डिफ़ॉल्ट मान


संक्षेप में, स्वचालित रूप से उत्पन्न और अद्यतन स्कीमा दृष्टिकोण की मुख्य कमियां:


  1. यह जावा-प्रथम है और फलस्वरूप SQL के संदर्भ में लचीला नहीं है, गैर-अनुमानित, पहले जावा पर उन्मुख है, और कभी-कभी SQL सामग्री को आपकी अपेक्षा के अनुरूप नहीं करता है। आप इसे संचालित करने के लिए कुछ SQL परिभाषाएँ लिख सकते हैं, लेकिन यह शुद्ध SQL DDL की तुलना में सीमित है।

  2. कभी-कभी मौजूदा तालिकाओं को अपडेट करना और डेटा के साथ कुछ करना असंभव होता है, और हमें वैसे भी SQL स्क्रिप्ट की आवश्यकता होती है। ज्यादातर मामलों में, यह स्वचालित स्कीमा अद्यतन और डेटा अद्यतन करने के लिए माइग्रेशन रखने के साथ समाप्त होता है। माइग्रेशन में डेटाबेस लेयर से संबंधित हर चीज को स्वचालित रूप से जेनरेट करने और करने से बचना हमेशा आसान होता है।


    साथ ही, जब समानांतर विकास की बात आती है तो यह सुविधाजनक नहीं है क्योंकि यह वर्जनिंग का समर्थन नहीं करता है, और यह बताना मुश्किल है कि स्कीमा के साथ क्या हो रहा है।

समाधान

स्कीमा को स्वचालित रूप से जेनरेट और अपडेट किए बिना यह कैसा दिखता है:


डीबी प्रारंभ करने के लिए स्क्रिप्ट:

संसाधन/डीबी/माइग्रेशन/V1__db_initialization.sql

 create table if not exists users ( id uuid not null primary key, user_name varchar(64) not null, email varchar(128) );


कुछ उपयोगकर्ताओं के साथ डेटाबेस भरना:

संसाधन/डीबी/माइग्रेशन/V2__users_some_data.sql

 insert into users (id, user_name, email) values ('297a848d-d406-4055-8a6f-4a4118a44001', 'Artem', null); insert into users (ID, USER_NAME, EMAIL) values ('921a9d42-bf14-4c3f-9893-60f79cdd0825', 'Antonio', '[email protected]');


नया फ़ील्ड जोड़ना और मौजूदा पंक्तियों में गैर-तुच्छ डिफ़ॉल्ट मान सेट करना:

संसाधन/डीबी/माइग्रेशन/V3__users_add_receive_notification.sql

 alter table users add column if not exists receive_notifications boolean; -- It's not a really safe with huge amount of data but good for the example update users set users.receive_notifications = email is not null; alter table users alter column receive_notifications set not null;


और अगर हम चुनते हैं तो कुछ भी हमें हाइबरनेट का उपयोग करने से नहीं रोकता है। कॉन्फ़िगरेशन में, हमें यह संपत्ति सेट करने की आवश्यकता है:

 jpa.hibernate.ddl-auto=validate


अब हाइबरनेट कुछ भी उत्पन्न नहीं करेगा। यह केवल जांच करेगा कि जावा प्रतिनिधित्व डीबी से मेल खाता है या नहीं। इसके अलावा, हमें हाइबरनेट स्वचालित जनरेट करने के लिए कुछ जावा और एसक्यूएल को मिलाने की आवश्यकता नहीं है, इसलिए यह संक्षिप्त और अतिरिक्त जिम्मेदारी के बिना हो सकता है:

 @Entity @Table(name = "users") public class User { @Id @Column(name = "id") @GeneratedValue private UUID id; @Column(name = "user_name", length = 64, nullable = false) private String userName; @Column(name = "email", length = 128, nullable = true) private String email; @Column(name = "receive_notifications", nullable = false) private Boolean receiveNotifications; }

माइग्रेशन का सही इस्तेमाल कैसे करें

  1. माइग्रेशन का प्रत्येक भाग निष्क्रिय होना चाहिए, जिसका अर्थ है कि यदि माइग्रेशन कई बार लागू होता है, तो डेटाबेस स्थिति वही रहती है। यदि हम इसे अनदेखा करते हैं, तो हम रोलबैक के बाद त्रुटियों के साथ समाप्त हो सकते हैं या उन टुकड़ों को लागू नहीं कर सकते हैं जो विफलताओं का कारण बनते हैं। ज्यादातर मामलों में, if exists if not exists / मौजूद है जैसे चेक जोड़कर आसानी से प्राप्त किया जा सकता है जैसा कि हमने ऊपर किया था।
  2. कुछ डीडीएल लिखते समय, एक माइग्रेशन में जितना संभव हो उतना जोड़ना बेहतर होता है, न कि कई बनाने के लिए। मुख्य कारण पठनीयता है। यह बेहतर है यदि संबंधित परिवर्तन, एक पुल अनुरोध में किए गए, एक फ़ाइल में हैं।
  3. पहले से मौजूद माइग्रेशन को न बदलें. यह एक स्पष्ट लेकिन आवश्यक है। एक बार माइग्रेशन लिखे जाने, मर्ज किए जाने और परिनियोजित करने के बाद इसे अछूता रहना चाहिए। कुछ संबंधित परिवर्तन अलग से किए जाने चाहिए।
  4. प्रत्येक डेवलपर को एक अलग वातावरण की आवश्यकता होती है। आमतौर पर, यह एक स्थानीय है। इसका कारण यह है कि यदि कुछ माइग्रेशन को साझा परिवेश पर लागू किया जाता है, तो बाद में माइग्रेशन इंस्ट्रूमेंट्स के काम करने के तरीके के कारण कुछ विफलताओं का पालन किया जाएगा।
  5. कुछ एकीकरण परीक्षण करना सुविधाजनक है जो एक परीक्षण डेटाबेस पर सभी माइग्रेशन चलाते हैं और जांचते हैं कि सब कुछ काम कर रहा है या नहीं। यह वास्तव में निर्माण में आसान हो सकता है जो विलय से पहले पीआर की शुद्धता की जांच करता है और बहुत सी प्राथमिक गलतियों से बचा जा सकता है। इस उदाहरण में, ऐसे एकीकरण परीक्षण हैं जो बॉक्स से बाहर की जाँच करते हैं।
  6. V{datetime}__description.sql का उपयोग करने के बजाय माइग्रेशन नामकरण के लिए V{version+=1}__description.sql पैटर्न का उपयोग करना बेहतर है। दूसरा सुविधाजनक है और समानांतर विकास में संस्करण संख्या संघर्ष से बचने में मदद करेगा। लेकिन कभी-कभी, संस्करणों को नियंत्रित किए बिना डेवलपर्स के सफलतापूर्वक माइग्रेशन लागू करने की तुलना में नाम का विरोध होना बेहतर है।

निष्कर्ष


यह बहुत सारी जानकारी थी, लेकिन मुझे आशा है कि आपको यह मददगार लगेगी। यदि आप स्कीमा को स्वचालित रूप से जेनरेट/अपडेट करने का उपयोग करते हैं - स्कीमा के साथ क्या हो रहा है, इस पर एक नज़र डालें क्योंकि यह अप्रत्याशित व्यवहार कर सकता है। और इसे संचालित करने के लिए जितना संभव हो उतना विवरण जोड़ना हमेशा एक अच्छा विचार है।


लेकिन अगली बार माइग्रेशन पर विचार करना बेहतर होगा क्योंकि यह जावा संस्थाओं को राहत देगा, अतिरिक्त जिम्मेदारी को हटा देगा, और डीडीएल पर बहुत अधिक नियंत्रण के साथ आपको लाभान्वित करेगा।


सर्वोत्तम प्रथाओं को सारांशित करने के लिए:

  • पलायन को निष्क्रिय लिखें।
  • एकीकरण परीक्षण लिखकर एक परीक्षण डेटाबेस पर सभी माइग्रेशन का एक साथ परीक्षण करें।
  • एक फ़ाइल में संबंधित परिवर्तन शामिल करें।
  • प्रत्येक डेवलपर को अपने स्वयं के डीबी पर्यावरण की आवश्यकता होती है।
  • माइग्रेशन लिखते समय संस्करणों पर करीब से नज़र डालें।
  • पहले से मौजूद लोगों को न बदलें।


आप GitHub पर पूरी तरह से काम करने वाला उदाहरण पा सकते हैं।